package dev.ece.suit.text;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang3.StringUtils;

import dev.ece.core.texture.Texture;
import dev.ece.core.vertex.VAO;
import dev.ece.util.GLUtils;
import dev.ece.util.lib.Rectangle;
import dev.ece.util.lib.Vector2f;
import dev.ece.util.lib.Vector3f;
import dev.ece.util.lib.Vector4f;
import dev.ece.util.tools.ploy.TextureEdgePloy;

public class TextLibrary {

	private int lineHeight;
	
	private Font font;
	
	private Texture texture;
	
	private final Map<Character, TextCharacter> charactors = new ConcurrentHashMap<Character, TextCharacter>();
	
	private VAO vao;
	
	private TextLibShader textLibShader;
	
	public TextLibrary(Font font, String text) {
		this(font, text.toCharArray());
	}
	
	public TextLibrary(Font font, char[] chars) {
		this.font = font;
		this.textLibShader = createTextLibShader();
		this.setup(chars);
	}
	
	protected TextLibShader createTextLibShader() {
		return TextLibShader.getInstance();
	}
	
	protected int computeLineHeight() {
		BufferedImage image = new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = (Graphics2D) image.getGraphics(); 
		g.setFont(font);
		g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_DEFAULT);
		FontMetrics fontMetrics = g.getFontMetrics();
		int lineHeight = fontMetrics.getHeight();
		g.dispose();
		return lineHeight;
	}
	
	/**
	 * 初始化文字纹理
	 */
	protected void setup(char[] chars) {
		int fontSize = this.font.getSize();
		int width = 1024 * fontSize / 16;
		int cols = width  / fontSize;
		int rows = chars.length / cols;
		if(chars.length % cols > 0) {
			rows ++;
		}
		this.lineHeight = computeLineHeight();
		int height = rows * this.lineHeight;
		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = (Graphics2D) image.getGraphics(); 
		g.setFont(font);
		g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_DEFAULT);
		g.setColor(Color.BLACK);
		FontMetrics fontMetrics = g.getFontMetrics();
		int col = 0;
		int row = 0;
		int x = 0;
		int y = 0;
		int fontWidth = 0;
		Character word = null;
		TextCharacter render = null;
		for(int i=0; i<chars.length; i++) {
			col = i % cols;
			row = i / cols;
			if(col == 0) {
				x = 0;
			}
			y = row * lineHeight;
			word = chars[i];
			fontWidth = fontMetrics.charWidth(word);
			g.drawChars(chars, i, 1, x, y + fontMetrics.getAscent());
			render = new TextCharacter(word, fontWidth, lineHeight, 
					new Rectangle(x / (float) width, y / (float) height, (x + fontWidth) / (float) width, (y + lineHeight) / (float) height));
			charactors.put(word, render);
			x += fontWidth;
		}
		this.texture = Texture.newInstance(image, TextureEdgePloy.getInstance());
	}
	
	public void initialize() {
		final List<Vector3f> vertice = new ArrayList<Vector3f>();
		final List<Vector2f> uvs = new ArrayList<Vector2f>();
		int index = 0;
		for(TextCharacter charactor: this.charactors.values()) {
			charactor.initialize(index ++, vertice, uvs);
		}
		this.vao = new VAO();
		this.vao.bind();
		this.vao.bindVertexArrayBuffer3f(vertice);
		this.vao.bindVertexArrayBuffer2f(uvs);
		this.vao.finish();
		vertice.clear();
		uvs.clear();
	}
	
	public TextCharacter getCharacter(char c) {
		return charactors.get(c);
	}
	
	public void renderSimpleText(String text, Vector4f color, Vector3f position) {
		renderSimpleText(text, this.getDefaultFontSize(), color, position);
	}
	
	public void renderSimpleText(String text, float fontSize, Vector4f color, Vector3f position) {
		renderSimpleText(text, fontSize, color, position, StringUtils.remove(text, "\n").length());
	}
	
	public void renderSimpleText(String text, Vector4f color, Vector3f position, int length) {
		renderSimpleText(text, this.getDefaultFontSize(), color, position, length);
	}
	
	public void renderSimpleText(String text, float fontSize, Vector4f color, Vector3f position, int length) {
		String[] lines = StringUtils.split(text, "\n");
		renderSimpleText(lines, fontSize, color, position, length);
	}
	
	public void renderSimpleText(String[] lines, Vector4f color, Vector3f position) {
		renderSimpleText(lines, this.getDefaultFontSize(), color, position);
	}
	
	public void renderSimpleText(String[] lines, float fontSize, Vector4f color, Vector3f position) {
		int length = 0;
		for(String line: lines) {
			length += line.length();
		}
		renderSimpleText(lines, fontSize, color, position, 0, lines.length, length);
	}
	
	public void renderSimpleText(String[] lines, Vector4f color, Vector3f position, int length) {
		renderSimpleText(lines, this.getDefaultFontSize(), color, position, length);
	}
	
	public void renderSimpleText(String[] lines, float fontSize, Vector4f color, Vector3f position, int length) {
		renderSimpleText(lines, fontSize, color, position, 0, lines.length, length);
	}
	
	public void renderSimpleText(String[] lines, Vector4f color, Vector3f position, int startLineIndex, int lineLength, int length) {
		renderSimpleText(lines, this.getDefaultFontSize(), color, position, startLineIndex, lineLength, length);
	}
	
	public void renderSimpleText(String[] lines, float fontSize, Vector4f color, Vector3f position, int startLineIndex, int lineLength, int length) {
		if(fontSize <= 0) {
			return;
		}
		if(this.texture == null) {
			return;
		}
		if(startLineIndex < lines.length) {
			GLUtils.glPushMatrix();
			try {
				GLUtils.translate(position);
				GLUtils.computeModelViewProjectionMatrix();
				this.textLibShader.glUseProgram();
				this.textLibShader.glModelViewProjectMatrix();
				this.textLibShader.glFontSize(fontSize);
				this.textLibShader.glDefaultFontSize(this.font.getSize());
				this.textLibShader.glColor(color);
				this.textLibShader.glTexture(texture);
				this.vao.bind();
				int index = Math.max(0, startLineIndex);
				int size = Math.min(startLineIndex + lineLength, lines.length);
				int charIndex = 0;
				float offset_y = - lineHeight / 2f;
				float offset_x = 0;
				float hwidth = 0;
				char[] chars = null;
				TextCharacter ch = null;
				for(int i=index; i<size; i++) {
					textLibShader.glOffsetY(offset_y);
					chars = lines[i].toCharArray();
					offset_x = 0;
					for(int j=0; j<chars.length; j++) {
						if(charIndex < length) {
							ch = this.charactors.get(chars[j]);
							hwidth = ch.getWidth() / 2f;
							offset_x += hwidth;
							textLibShader.glOffsetX(offset_x);
							ch.render(null, null);
							offset_x += hwidth;
						} else {
							return;
						}
						charIndex ++;
					}
					offset_y -= lineHeight;
				}
			} finally {
				GLUtils.glPopMatrix();
			}
		}
	}
	
	public RangeColorText createTangeColorText(String[] lines) {
		return RangeColorText.createTangeColorText(lines, this);
	}
	
	public RangeColorText createTangeColorText(String text) {
		return RangeColorText.createTangeColorText(text, this);
	}
	
	public void renderColorText(RangeColorText colorText, Vector3f position) {
		renderColorText(colorText, this.font.getSize2D(), position, 0, colorText.length(), colorText.length());
	}
	
	public void renderColorText(RangeColorText colorText, float fontSize, Vector3f position) {
		renderColorText(colorText, fontSize, position, 0, colorText.length(), colorText.length());
	}
	
	public void renderColorText(RangeColorText colorText, float fontSize, Vector3f position, int length) {
		renderColorText(colorText, fontSize, position, 0, colorText.length(), length);
	}
	
	public void renderColorText(RangeColorText colorText, float fontSize, Vector3f position, int startLineIndex, int lineLength, int length) {
		if(fontSize <= 0) {
			return;
		}
		if(this.texture == null) {
			return;
		}
		GLUtils.glPushMatrix();
		try {
			GLUtils.translate(position);
			GLUtils.computeModelViewProjectionMatrix();
			this.textLibShader.glUseProgram();
			this.textLibShader.glModelViewProjectMatrix();
			this.textLibShader.glFontSize(fontSize);
			this.textLibShader.glDefaultFontSize(this.font.getSize());
			this.textLibShader.glColor(colorText.getDefaultColor());
			this.textLibShader.glTexture(texture);
			this.vao.bind();
			colorText.render(textLibShader, startLineIndex, lineLength, length, this.lineHeight);
		} finally {
			GLUtils.glPopMatrix();
		}
	}
	
	public Font getFont() {
		return font;
	}

	public int getDefaultFontSize() {
		return this.font.getSize();
	}
	
	public float getCharsWidth(String text, int fontSize) {
		return getCharsWidth(text.toCharArray(), fontSize);
	}
	
	public float getCharsWidth(char[] chars, int fontSize) {
		float width = 0;
		for(char c: chars) {
			width += charactors.get(c).getWidth() * fontSize / getDefaultFontSize();
		}
		return width;
	}
	
	public float getCharWidth(char c, int fontSize) {
		return charactors.get(c).getWidth() * fontSize / getDefaultFontSize();
	}
	
	public float getLineHeight() {
		return this.lineHeight;
	}
	
	public float getLineHeight(int fontSize) {
		return this.lineHeight * fontSize / (float) this.font.getSize();
	}
	
	public float getHeight(RangeColorText text) {
		return getHeight(getFont().getSize(), text);
	}
	
	public float getHeight(int fontSize, RangeColorText text) {
		return this.lineHeight * fontSize * text.getLines().length / (float) this.font.getSize();
	}
	
	public void destroy() {
		if(this.texture != null) {
			this.texture.destroy();
			this.texture = null;
		}
		if(!charactors.isEmpty()) {
			Iterator<Entry<Character, TextCharacter>> it = charactors.entrySet().iterator();
			while(it.hasNext()) {
				it.next().getValue().destroy();
			}
			charactors.clear();
		}
	}
	
}
